#include "matrix.h"
#include "device.h"
#include "semaphore.h"
#include "ringbuffer.h"
#include "error.h"
#include "interrupt.h"
#include "stm32f4xx_usart.h"

#define UART2_BUF_SIZE 1024

static mx_usize_t rtimeout = 0;                 // read timeout (tick)
static mx_semaphore_t wsem;                     // write opeartion semaphore
static mx_ringbuffer_t rbuf;                    // receive ringbuffer
static mx_uint8_t buf[UART2_BUF_SIZE];
static volatile mx_bool_t wflag = mx_false;     // wait flag

static mx_err_t uart2_open (mx_device_pt pdev)
{
    /* GPIO configuration */
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
 
    /* USART2 Pin alternation function */
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_USART2);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource6, GPIO_AF_USART2);
    
    /* USART2 Port configuration */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    /* USART2 initialization */
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART2, &USART_InitStructure);
    
    USART_Cmd(USART2, ENABLE);
    USART_ClearFlag(USART2, USART_FLAG_TC);
    
    /* Enable RXNE interrupt */
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

    /* Usart2 NVIC configuration */
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;   // disabled preemption priority
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;          // can't be 0, 0 is for PendSV
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    return EOK;
}

static void uart2_close (mx_device_pt pdev)
{
    USART_Cmd(USART2, DISABLE);
    mx_ringbuffer_clear(&rbuf);
}

static mx_size_t uart2_read (mx_device_pt pdev, mx_void_pt buf, mx_size_t size)
{
    mx_size_t ret = 0;
    mx_usize_t cnt = mx_ringbuffer_count(&rbuf);
    if (cnt == 0)
    {
        wflag = mx_true;
        if (rtimeout == 0)
        {
            mx_semaphore_waitforever(&wsem);
        }
        else
        {
            mx_semaphore_wait(&wsem, rtimeout);
        }
        cnt = mx_ringbuffer_count(&rbuf);
    }
    if (cnt > 0)
    {
        ret = size > cnt ? cnt : size;
        for (mx_size_t i = 0; i < ret; i++)
        {
            ((mx_uint8_pt)buf)[i] = (mx_uint8_t)mx_ringbuffer_getchar(&rbuf);
        }
    }
    return ret;
}

static mx_size_t uart2_write (mx_device_pt pdev, mx_void_pt buf, mx_size_t size)
{
    for(mx_size_t i = 0; i < size; i++)
    {
        USART_SendData(USART2, (mx_uint8_t)((mx_uint8_pt)buf)[i]);
        while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    }
}

static mx_err_t uart2_ctrl (mx_device_pt pdev, mx_size_t ctrl, mx_void_pt arg)
{
    mx_err_t ret = EERR;
    switch (ctrl)
    {
    case 1: // read timeout configuration
        rtimeout = (mx_usize_t)arg;
        ret = EOK;
        break;
    
    default:
        break;
    }
    return ret;
}

static mx_device_ops_t ops = {
    .open = uart2_open,
    .close = uart2_close,
    .read = uart2_read,
    .write = uart2_write,
    .ctrl = uart2_ctrl
};

/* uart2 interrupt handler */
static void uart2_intr_handler (mx_void_pt param)
{
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
        mx_uint16_t data = USART_ReceiveData(USART2);
        mx_ringbuffer_putchar(&rbuf, (mx_uint8_t)data);
        if (wflag)
        {
            wflag = mx_false;
            mx_semaphore_sign(&wsem);
        }
    }
}

static mx_device_t dev_uart2;

static mx_device_data_t devdata = {
    .name = "uart2",
    .drvname = ""
};

void device_uart_init ()
{
    /* initialize semaphore */
    mx_semaphore_init(&wsem, 1, 0);
    /* initialize ringbuffer */
    mx_ringbuffer_init(&rbuf, buf, UART2_BUF_SIZE);
    /* interrupt register */
    mx_interrupt_register("uart2-int54", 54, uart2_intr_handler, mx_null);
    /* register uart interrupt handler to system */
    mx_device_mount(&dev_uart2, &devdata, &ops);
}
MX_BOARDINIT_EXPORT(device_uart_init, 1);
